/**
 * @description: 钉钉授权登录及用户信息获取 service
 * @version: 0.0.1
 * @author: z..
 * @Date: 2023-06-12 18:16:39
 */

import { Config } from '@midwayjs/decorator';
import { Provide, Inject, httpError } from '@midwayjs/core';
import { ApiDingtalk } from '../utils/api.dingtalk';
import { OapiDingtalk } from '../utils/oapi.dingtalk';
import { UserRecordsModel } from "../entity/user_records.method";
import { AppConfigModel } from "../entity/app_config.method";
import { JwtService } from '@midwayjs/jwt';
import { JsonConfigModel } from '../entity/json_config.method'
// import * as fs from 'fs';


@Provide()
export class DingtalkAuthService {

    @Config('keywords')
    keywords: any;

    @Config('dingtalkAuthConfig')
    dingtalkAuthConfig: any;

    @Config('appConfig')
    appConfig: any;

    @Inject()
    ApiDingtalk: ApiDingtalk;

    @Inject()
    OapiDingtalk: OapiDingtalk;

    @Inject()
    UserRecordsModel: UserRecordsModel;

    @Inject()
    AppConfigModel: AppConfigModel;

    @Inject()
    jwtService: JwtService;

    @Inject()
    JsonConfigModel: JsonConfigModel

    /**
     * 钉钉OAuth登录授权页面链接获取
     * 文档地址：https://open.dingtalk.com/document/isvapp-server/obtain-identity-credentials
     * redirect_uri     授权通过/拒绝后回调地址。
     * response_type    固定值为code。授权通过后返回authCode。
     * client_id    appkey
     * scope    openid：授权后可获得用户userid  openid corpid：授权后可获得用户id和登录过程中用户选择的组织id，空格分隔。注意url编码。
     * state    跟随authCode原样返回。
     * prompt   值为consent时，会进入授权确认页。
     * @returns 
     */
    async getRedirectUri(state: string): Promise<any> {
        const redirect_uri = this.dingtalkAuthConfig.redirect_uri || 'https://morefun-oa.morefun.me/login';
        const url = `${this.dingtalkAuthConfig.login_uri}?redirect_uri=${encodeURIComponent(redirect_uri)}&response_type=code&client_id=${this.appConfig.appKey}&scope=openid&state=${state}&prompt=consent`
        return url;
    }


    /**
       * 钉钉用户授权登录获取token
       * @param {*} authCode 
       * @returns 
       */
    async getUserInfo({ authCode, state }: { authCode: string, state: any }): Promise<any> {
        //  获取用户钉钉授权token
        const tokenData = await this.ApiDingtalk.userAccessToken(this.appConfig.appKey, authCode, this.appConfig.appSecret);

        if (!tokenData.success || !tokenData.accessToken) {
            return tokenData;
        }

        //  计算token过期时间
        const expiresTime = new Date().getTime() + tokenData.expireIn * 1000;

        //  获取用户通讯录个人信息
        const userData = await this.ApiDingtalk.contactUsers(tokenData.accessToken);
        if (!userData.success) throw new httpError.BadRequestError("获取个人信息失败");

        //  查询当前用户是否是上海莫凡的在职员工
        const userList = await this.getOnJobUserLists();
        if(!userList.length) throw new httpError.BadRequestError("获取员工信息失败");
        if(!userList.includes(userData.unionId)) throw new httpError.BadRequestError("您不是当前组织的在职员工");

        //  获取apptoken
        const appToken = await this.getAppToken();
        if (!appToken || !appToken.success) throw new httpError.BadRequestError("获取应用token失败");
        //  验证当前用户是否为上海莫凡的在职员工
        

        //  若无头像则启用默认头像
        if (!userData.avatarUrl) {
            userData.avatarUrl = "https://morefun-active.oss-cn-beijing.aliyuncs.com/morefun_oa/mofun_OA_img/logo_img.png";
        }

        //  获取jwtToken
        const _jwtToken = await this.getJwtToken(userData.unionId, tokenData.accessToken, tokenData.expireIn);

        //  查询当前用户信息是否存在
        const _userInfo = await this.UserRecordsModel.getUserUnionId(userData.unionId);
        

        if (_userInfo && _userInfo.length) {
            /**
             * 更新用户信息
             */
            let _contactType = _userInfo[0].contact_type || "";
            let _userId = _userInfo[0].user_id || "";
            if (!_userInfo[0].user_id) {
                // 获取当前用户的userid
                const { contact_type, userid } = await this.OapiDingtalk.getByUnionid(userData.unionId, appToken.accessToken);
                _contactType = String(contact_type) || "";
                _userId = userid || "";
            }

            //  更新用户信息
            await this.UserRecordsModel.updateUserInfo({
                id: _userInfo[0].id,
                ...userData,
                accessToken: tokenData.accessToken,
                refreshToken: tokenData.refreshToken,
                expiresTime: expiresTime,
                contactType: _contactType,
                userId: _userId,
            });
        } else {
            /**
             * 新增用户信息
             */
            // 获取当前用户的userid
            const { contact_type, userid } = await this.OapiDingtalk.getByUnionid(userData.unionId, appToken.accessToken);

            //  新增用户信息
            await this.UserRecordsModel.insertUserInfo({
                ...userData,
                accessToken: tokenData.accessToken,
                refreshToken: tokenData.refreshToken,
                expiresTime: expiresTime,
                contactType: contact_type || "",
                userid: userid || "",
            });
        }

        //  查询用户数据并返回
        const userRes = await this.UserRecordsModel.getUserUnionId(userData.unionId);
        return {
            nick: userRes[0].nick,
            avatar_url: userRes[0].avatar_url,
            dept_id_list: userRes[0].dept_id_list || "",
            is_admin: userRes[0].is_admin || 0,
            user_status: userRes[0].user_status,
            oaToken: _jwtToken
        };
    };

    /**
     * 生成jwtToken
     * @param {*} union_id 
     * @param {*} access_token 
     * @param {*} expire_in 
     * @returns 
     */
    async getJwtToken(union_id: string, access_token: string, expire_in: number): Promise<any> {
        // const res = jwt.sign({
        //     data: {
        //         unionId: union_id,
        //         accessToken: access_token
        //     }
        // }, _priKey, { expiresIn: `${expire_in}s` });
        // const privateKey = fs.readFileSync(resolve(__dirname, '../config/keys/hs256.key'), 'utf-8');

        return await this.jwtService.sign({
            data: {
                unionId: union_id,
                accessToken: access_token
            }
        }, { expiresIn: `${expire_in}s` });
    }

    /**
     * 获取企业内部应用的access_token
     * @returns 
     */
    async getAppToken(): Promise<any> {
        const appRes = await this.AppConfigModel.getDataByAppkey(this.appConfig.appKey);
        if (appRes && appRes[0] && appRes[0].access_token) {
            // token 未过期 直接返回
            if (Number(appRes[0].expires_time) > new Date().getTime()) {
                console.log("#######  token 未过期  ######");
                return { success: true, accessToken: appRes[0].access_token }
            }
            console.log("#######  token 已过期-需要重新获取  ######");
            const { success, access_token, expires_in } = await this.OapiDingtalk.obtainOrgappToken(this.appConfig.appKey, this.appConfig.appSecret);
            if (!success) return { success: false, msg: "获取企业token失败" };
            const _expiresTime = new Date().getTime() + expires_in * 1000;
            //  更新应用token数据
            await this.AppConfigModel.updateTokenById({
                id: appRes[0].id,
                accessToken: access_token,
                expiresTime: _expiresTime
            });
            return { success: true, accessToken: access_token };
        } else {
            const { success, access_token, expires_in } = await this.OapiDingtalk.obtainOrgappToken(this.appConfig.appKey, this.appConfig.appSecret);
            if (!success) return { success: false, msg: "获取企业token失败" };
            const _expiresTime = new Date().getTime() + expires_in * 1000;
            //  存储应用token数据
            await this.AppConfigModel.insertAppInfo({
                ...this.appConfig,
                accessToken: access_token,
                expiresTime: _expiresTime
            });
            return { success: true, accessToken: access_token };
        }
    }

    /**
     * 获取在职员工的unionid列表
     * @returns 
     */
    async getOnJobUserLists(): Promise<any> {
        let _users = [];
        try{
            const configInfo = await this.JsonConfigModel.getDataByCode('OnJobUser');
            if (configInfo && configInfo.length && configInfo[0].json_data) {
                let _arr = configInfo[0].json_data ? JSON.parse(configInfo[0].json_data) : [];
                for(let i = 0; i < _arr.length; i++){
                    _users.push(_arr[i].unionid)
                }
            }
            return _users;
        }catch(e){
            return []
        }
    }

}